﻿using System;
using System.IO;
using System.Reflection;

namespace LuaVM.Core
{
	/// <summary>
	/// 以Dll方式动态加载的插件，文件名必须以"Plugin."开始
	/// </summary>
	public abstract class LuaDllPlugin : LuaPlugin
	{
		/// <summary>
		/// 插件的帮助手册，可以是文件名或url
		/// </summary>
		public virtual string HelpFile { get; }

		/// <summary>
		/// DLL文件路径（如果插件是通过LoadFromDll加载的）
		/// </summary>
		public string DllPath { get; private set; }

		/// <summary>
		/// DLL文件名前缀
		/// </summary>
		public const string PluginPrefix = "plugin.";		

		/// <summary>
		/// 从指定的dll文件中寻找第一个LuaDllPlugin继承类并创建一个实例
		/// </summary>
		/// <param name="dllPath">dll文件路径</param>
		/// <returns>加载成功返回创建的LuaDllPlugin继承类实例，失败返回null</returns>
		public static LuaDllPlugin LoadFromDll(string dllPath)
		{
			string name = IsPluginDll(dllPath);
			if (name == null)
			{
				throw new ArgumentException("dllPath is required.");
			}

			Type type = FindPluginTypeFromDll(dllPath);
			if (type == null)
			{
				throw new Exception("Cannot find a LuaDllPlugin subclass in dll: " + dllPath);
			}

			ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
			if (ci == null)
			{
				throw new Exception("Cannot locate default constructor in class: " + type.Name);
			}

			LuaDllPlugin plugin = (LuaDllPlugin)(ci.Invoke(new object[] { }));
			if (plugin == null)
			{
				throw new Exception("Cannot construct a instance of: " + type.Name);
			}

			plugin.DllPath = dllPath;
			return plugin;
		}

		/// <summary>
		/// 检查一个dll是否为有效插件dll
		/// </summary>
		/// <param name="dllPath">dll文件路径</param>
		/// <returns>检查通过返回名称，失败返回null</returns>
		public static string IsPluginDll(string dllPath)
		{
			if (string.IsNullOrEmpty(dllPath))
			{
				return null;
			}

			string fileName = Path.GetFileNameWithoutExtension(dllPath);
			if (!fileName.ToLower().StartsWith(PluginPrefix))
			{
				return null;
			}

			if (FindPluginTypeFromDll(dllPath) == null)
			{
				return null;
			}

			return fileName.Substring(PluginPrefix.Length);
		}

		// 从dll文件中找到第一个LuaDllPlugin的继承类		
		private static Type FindPluginTypeFromDll(string dllPath)
		{
			Assembly asm = Assembly.LoadFile(dllPath);
			if (asm == null)
			{
				return null;
			}

			Type baseType = typeof(LuaDllPlugin);

			Type[] types = asm.GetExportedTypes();
			foreach (Type type in types)
			{
				if (type.IsSubclassOf(baseType))
				{
					return type;
				}
			}

			return null;
		}
	}
}
